﻿using System;
using System.Collections.Generic;
using System.Windows.Forms.Design;
using System.Drawing.Design;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;

namespace UnrealDebugger
{
    [Serializable]
    public class FileRef
    {
        [Category("File")]
        [DisplayName("File Name")]
        [Description(@"File Name")]
        public string FileName { get; set; }

        [Category("File")]
        [DisplayName("Line Number")]
        [Description(@"Line number")]
        public int LineNo { get; set; }

        public FileRef()
        {
        }

        public FileRef(string fileName, int lineNo)
        {
            this.FileName = fileName;
            this.LineNo = lineNo;
        }
    }

    [Serializable]
    public class Breakpoint
    {
        [Category("Breakpoint")]
        [DisplayName("Class Name")]
        [Description(@"Class name")]
        public string ClassName { get; set; }

        [Category("Breakpoint")]
        [DisplayName("Line Number")]
        [Description(@"Line number")]
        public int LineNo { get; set; }

        [Category("Breakpoint")]
        [DisplayName("Healthy")]
        [Description(@"Healthy")]
        public bool Healthy { get; set; }

        [Category("Breakpoint")]
        [DisplayName("Enabled")]
        [Description(@"Enabled")]
        public bool Enabled { get; set; }

        public Breakpoint()
        {
            this.Healthy = false;
            this.Enabled = true;
        }

        public Breakpoint(string className, int lineNo)
        {
            this.ClassName = className;
            this.LineNo = lineNo;
            this.Healthy = false;
            this.Enabled = true;
        }
    }

    [Serializable]
    public class Project
    {
        #region Public Properties

        private bool _checkForUpdates = true;
        private bool _breakOnFirstLine = true;
        private bool _editWatchValues = false;
        private Font _editorFont = new Font("Tahoma", 8.25F);
        private string _projectPath = @"..\..\Development\Src";
        private string _fileExtensionFilter = @".uc;.uci";
        private List<FileRef> _autoFiles = new List<FileRef>();
        private MultiMap<string, Breakpoint> _breakpoints = new MultiMap<string, Breakpoint>();
        private SerializableDictionary<string, string> _userdata = new SerializableDictionary<string, string>();

        [Browsable(false)]
        public string EditorFontName
        {
            get 
            {
                TypeConverter converter = TypeDescriptor.GetConverter(typeof(Font));
                return converter.ConvertToString(_editorFont);
            }

            set
            {
                try
                {
                    TypeConverter converter = TypeDescriptor.GetConverter(typeof(Font));
                    _editorFont = (Font)converter.ConvertFromString(value);
                    
                    UnrealDebuggerIDE.Instance.RaiseEditorPropertiesChanged();
                }
                catch (Exception /*e*/)
                {
                    _editorFont = new Font("Tahoma", 8.25F);
                }
            }
        }

        [XmlIgnore]
        [Category("Editor")]
        [DisplayName("Editor Font")]
        [Description(@"Font used by the text editor")]
        [EditorAttribute(typeof(FontEditor), typeof(UITypeEditor))]
        public Font EditorFont
        {
            get { /* lock (this) */ { return _editorFont; } }
            set { /* lock (this) */ { _editorFont = value; } UnrealDebuggerIDE.Instance.RaiseEditorPropertiesChanged(); }
        }

        [Category("Editor")]
        [DisplayName("Check for Updates")]
        [Description(@"Enables automatic check for UnrealDebugger updates")]
        public bool CheckForUpdates
        {
            get { /* lock (this) */ { return _checkForUpdates; } }
            set { /* lock (this) */ { _checkForUpdates = value; } UnrealDebuggerIDE.Instance.RaiseEditorPropertiesChanged(); }
        }

        [Category("Editor")]
        [DisplayName("Break On First Line")]
        [Description(@"Breaks execution on the first byte code")]
        public bool BreakOnFirstLine
        {
          get { /* lock (this) */ { return _breakOnFirstLine; } }
          set { /* lock (this) */ { _breakOnFirstLine = value; } UnrealDebuggerIDE.Instance.RaiseEditorPropertiesChanged(); }
        }

        [Category("Editor")]
        [DisplayName("Edit Watch Values")]
        [Description(@"Enables editing the value of watched variables")]
        public bool EditWatchValues
        {
            get { /* lock (this) */ { return _editWatchValues; } }
            set { /* lock (this) */ { _editWatchValues = value; } UnrealDebuggerIDE.Instance.RaiseEditorPropertiesChanged(); }
        }

        [Category("Paths")]
        [DisplayName("Project Path")]
        [Description(@"Path where the script files are located. It must point to the Development\Src folder of the UDK")]
        [EditorAttribute(typeof(FolderNameEditor), typeof(UITypeEditor))]
        public string ProjectPath
        {
            get { /* lock (this) */ { return _projectPath; } }
            set 
            { 
                /* lock (this) */ 
                { _projectPath = value; }
                UnrealDebuggerIDE.Instance.RaiseProjectPathChanged(_projectPath, _fileExtensionFilter.Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries)); 
            } 
        }

        [Category("Paths")]
        [DisplayName("File Extension Filter")]
        [Description(@"Filter for selecting which files are shown in the Explorer Panel. Separate each extension by a semicolon")]
        public string FileExtensionFilter
        {
            get { /* lock (this) */ { return _fileExtensionFilter; } }
            set 
            { 
                /* lock (this) */ 
                { _fileExtensionFilter = value; }
                UnrealDebuggerIDE.Instance.RaiseProjectPathChanged(_projectPath, _fileExtensionFilter.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries));
            }
        }

        [Browsable(false)]
        public List<FileRef> AutoFiles
        {
          get { /* lock (this) */ { return _autoFiles; } }
          set { /* lock (this) */ { _autoFiles = value; } }
        }

        [Browsable(false)]
        public MultiMap<string, Breakpoint> Breakpoints
        {
            get { /* lock (this) */ { return _breakpoints; } }
            set { /* lock (this) */ { _breakpoints = value; } UnrealDebuggerIDE.Instance.RaiseBreakpointsChanged(value); } 
        }

        [Browsable(false)]
        public SerializableDictionary<string, string> UserData
        {
            get { /* lock (this) */ { return _userdata; } }
            set { /* lock (this) */ { _userdata = value; } }
        }

        #endregion

        public Project()
        {
        }

        #region Debugger Interface

        public Breakpoint FindBreakpoint(string className, int lineNo)
        {
            List<Breakpoint> breakpoints = _breakpoints[className.ToUpper()];
            Breakpoint breakpoint = breakpoints.Find(
                delegate(Breakpoint that) { return string.Compare(that.ClassName, className, true) == 0 && that.LineNo == lineNo; }
                );

            return breakpoint;
        }

        public Breakpoint AddBreakpoint(string className, int lineNo)
        {
            /* lock (this) */
            {
                Breakpoint breakpoint = FindBreakpoint(className, lineNo);

                if (breakpoint == null)
                {
                    breakpoint = new Breakpoint(className, lineNo);
                    _breakpoints.Add(className.ToUpper(), breakpoint);
                }

                UnrealDebuggerIDE.Instance.RaiseBreakpointsChanged(_breakpoints);

                return breakpoint;
            }
        }

        public Breakpoint EnableBreakpoint(string className, int lineNo, bool enabled)
        {
            /* lock (this) */
            {
                Breakpoint breakpoint = FindBreakpoint(className, lineNo);

                if (breakpoint == null)
                {
                    breakpoint = new Breakpoint(className, lineNo);
                    _breakpoints.Add(className.ToUpper(), breakpoint);
                }

                breakpoint.Enabled = enabled;
                UnrealDebuggerIDE.Instance.RaiseBreakpointsChanged(_breakpoints);

                return breakpoint;
            }
        }

        public Breakpoint ConnectBreakpoint(string className, int lineNo, bool connected)
        {
            /* lock (this) */
            {
                Breakpoint breakpoint = FindBreakpoint(className, lineNo);

                if (breakpoint == null)
                {
                    breakpoint = new Breakpoint(className, lineNo);
                    _breakpoints.Add(className.ToUpper(), breakpoint);
                }

                breakpoint.Healthy = connected;
                UnrealDebuggerIDE.Instance.RaiseBreakpointsChanged(_breakpoints);

                return breakpoint;
            }
        }

        public void RemoveBreakpoint(string className, int lineNo)
        {
            /* lock (this) */
            {
                List<Breakpoint> breakpoints = _breakpoints[className.ToUpper()];
                breakpoints.RemoveAll(
                    delegate(Breakpoint that) { return string.Compare(that.ClassName, className, true) == 0 && that.LineNo == lineNo; }
                    );

                UnrealDebuggerIDE.Instance.RaiseBreakpointsChanged(_breakpoints);
            }
        }

        public void ClearBreakpoints()
        {
            /* lock (this) */
            {
                _breakpoints = new MultiMap<string, Breakpoint>();
                UnrealDebuggerIDE.Instance.RaiseBreakpointsChanged(_breakpoints);
            }
        }

        #endregion

        #region Serialization

        public bool SaveToXml(string file)
        {
            try
            {
                UnrealDebuggerIDE.Instance.RaiseWriteUserData(this._userdata);

                XmlSerializer xml = new XmlSerializer(typeof(Project));

                using (TextWriter fs = new StreamWriter(file))
                {
                    xml.Serialize(fs, this);
                }

                return true;
            }
            catch (Exception e)
            {
                ExceptionMessageBox msgBox = new ExceptionMessageBox(e);
                msgBox.ShowDialog();
                return false;
            }
        }

        public static Project LoadFromXml(string file)
        {
            try
            {
                Project project = null;
                XmlSerializer xml = new XmlSerializer(typeof(Project));

                using (TextReader fs = new StreamReader(file))
                {
                    project = xml.Deserialize(fs) as Project;
                }

                UnrealDebuggerIDE.Instance.RaiseReadUserData(project._userdata);

                return project;
            }
            catch (Exception e)
            {
        		ExceptionMessageBox msgBox = new ExceptionMessageBox(e);
		        msgBox.ShowDialog();

                return new Project();
            }
        }

        public static Project LoadFromXml(Stream stream)
        {
            try
            {
                Project project = null;
                XmlSerializer xml = new XmlSerializer(typeof(Project));

                using (TextReader fs = new StreamReader(stream))
                {
                    project = xml.Deserialize(fs) as Project;
                }

                UnrealDebuggerIDE.Instance.RaiseReadUserData(project._userdata);

                return project;
            }
            catch (Exception e)
            {
                ExceptionMessageBox msgBox = new ExceptionMessageBox(e);
                msgBox.ShowDialog();

                return new Project();
            }
        }

        #endregion

        internal void Loaded()
        {
            UnrealDebuggerIDE.Instance.RaiseProjectLoaded(this._projectPath);
            UnrealDebuggerIDE.Instance.RaiseProjectPathChanged(this._projectPath, _fileExtensionFilter.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries));

            foreach (FileRef file in this._autoFiles)
            {
                Panels.Document document = UnrealDebuggerIDE.Instance.GetDocumentFromFile(file.FileName);
                if (document != null)
                {
                    document.GotoLine(file.LineNo, 0);
                    UnrealDebuggerIDE.Instance.ShowDocument(document);
                }
            }

            foreach (string key in this._breakpoints.Keys)
            {
                foreach (Breakpoint breakpoint in this._breakpoints[key])
                {
                    if (breakpoint.Enabled)
                    {
                        UnrealDebuggerIDE.Commands.ConnectBreakpoint(breakpoint.ClassName, breakpoint.LineNo);
                    }
                }
            }
        }
    }
}
